// 文件系统操作，用于读取配置文件
import { readFileSync } from "node:fs";
// YAML 解析库，将 YAML 格式的配置文件转换为 JavaScript 对象
import * as yaml from "js-yaml";
// 路径处理工具，用于构建配置文件路径
import { join } from "node:path";
// Lodash 工具库，用于深度合并配置对象
import * as _ from "lodash";
// Joi 验证库，用于验证配置数据
import * as joi from "joi";

// 基础配置文件名称，包含所有环境共享的默认配置
const YAML_COMMON_CONFIG_FILENAME = "config.yml";

// 构建基础配置文件的完整路径，使用项目根目录确保路径正确
const filePath = join(process.cwd(), "config", YAML_COMMON_CONFIG_FILENAME);
// 构建环境特定配置文件的路径，根据 NODE_ENV 环境变量确定，默认为 development
const envPath = join(process.cwd(), "config", `config.${process.env.NODE_ENV || "development"}.yml`);

// 读取并解析基础配置文件，包含所有环境的默认配置项
const commonConfig = yaml.load(readFileSync(filePath, "utf-8")) as Record<string, unknown>;
// 读取并解析环境特定配置文件，用于覆盖或补充基础配置
const envConfig = yaml.load(readFileSync(envPath, "utf-8")) as Record<string, unknown>;

// 定义配置验证 schema
const configSchema = joi
  .object({
    db: joi.object().unknown(true).optional(),
    mongo: joi
      .object({
        uri: joi
          .string()
          .uri({ scheme: ["mongodb", "mongodb+srv"] })
          .required(),
      })
      .optional(),
    NODE_ENV: joi.string().valid("development", "production").required(),
    PORT: joi.number().default(3000),
  })
  .unknown(true);

/**
 * NestJS 配置工厂函数
 *
 * 该函数用于加载和合并应用程序配置，支持分环境配置管理。
 * 配置加载顺序：先加载基础配置(config.yml)，然后用环境特定配置覆盖。
 * 增加了对合并后配置的 joi 验证，确保配置数据的完整性和正确性。
 *
 * @returns {Record<string, unknown>} 合并并验证后的完整配置对象
 * @throws {Error} 当配置验证失败时抛出错误
 *
 * @example
 * 配置文件结构：
 * - config/config.yml (基础配置)
 * - config/config.development.yml (开发环境配置)
 * - config/config.production.yml (生产环境配置)
 *
 * 使用方式：
 * ```typescript
 * import { ConfigModule } from '@nestjs/config';
 * import configuration from './configuration';
 *
 * ConfigModule.forRoot({
 *   load: [configuration],
 * })
 * ```
 */
export default () => {
  // 合并基础配置和环境特定配置
  const mergedConfig = _.merge(commonConfig, envConfig);

  // 验证合并后的配置
  const { error, value } = configSchema.validate(mergedConfig, {
    abortEarly: false,
    allowUnknown: true,
  });

  if (error) {
    throw new Error(`配置验证失败: ${error.details.map((detail) => detail.message).join(", ")}`);
  }

  return value;
};
